.NET: Align file tool descriptions with generated schema argument names - #7675
.NET: Align file tool descriptions with generated schema argument names#7675Ruiming Zhao (uuzzrm) wants to merge 1 commit into
Conversation
The file_access and file_memory tool descriptions referenced arguments by snake_case names (glob_pattern, old_string, new_string, replace_all) while AIFunctionFactory generates the tool schema from the camelCase C# parameter names. A model that follows the description ends up emitting argument names that never bind, so filters were silently dropped and replace calls failed for reasons unrelated to the file. Renamed the references to match the schema (globPattern, oldString, newString, replaceAll). The replace_lines descriptions are untouched: line_number and new_line are explicitly mapped via JsonPropertyName and are genuinely snake_case in the schema. Added regression tests asserting that each affected tool description and its generated schema agree on argument names for both providers.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds regression tests to ensure file tool descriptions reference the same argument names as the JSON schema generated by AIFunctionFactory, and updates tool [Description] strings to match the schema naming.
Changes:
- Added unit tests validating tool description argument names for FileAccess and FileMemory tools.
- Updated
[Description]attributes in FileAccess/FileMemory providers to use schema-aligned camelCase argument names (e.g.,globPattern,oldString). - Ensured explicitly-mapped snake_case argument names (via
JsonPropertyName) remain referenced as-is in descriptions.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/FileMemory/FileMemoryToolDescriptionSchemaTests.cs | New tests asserting FileMemory tool descriptions match JSON schema parameter names. |
| dotnet/tests/Microsoft.Agents.AI.UnitTests/Harness/FileAccess/FileAccessToolDescriptionSchemaTests.cs | New tests asserting FileAccess tool descriptions match JSON schema parameter names. |
| dotnet/src/Microsoft.Agents.AI/Harness/FileMemory/FileMemoryProvider.cs | Updated tool descriptions to use schema argument names (globPattern, oldString, etc.). |
| dotnet/src/Microsoft.Agents.AI/Harness/FileAccess/FileAccessProvider.cs | Updated tool descriptions to use schema argument names (globPattern, oldString, etc.). |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| private static async Task<AIFunction> GetToolAsync(string toolName) | ||
| { | ||
| // Arrange | ||
| var provider = new FileAccessProvider(new InMemoryAgentFileStore()); | ||
| var agent = new Mock<AIAgent>().Object; | ||
| var session = new ChatClientAgentSession(); | ||
| #pragma warning disable MAAI001 | ||
| var context = new AIContextProvider.InvokingContext(agent, session, new AIContext()); | ||
| #pragma warning restore MAAI001 | ||
|
|
||
| AIContext result = await provider.InvokingAsync(context); | ||
| return (AIFunction)result.Tools!.First(t => t is AIFunction f && f.Name == toolName); | ||
| } | ||
|
|
||
| private static List<string> GetSchemaArgumentNames(AIFunction tool) | ||
| { | ||
| var names = new List<string>(); | ||
| if (tool.JsonSchema is JsonElement schema) | ||
| { | ||
| CollectPropertyNames(schema, names); | ||
| } | ||
|
|
||
| return names.Distinct().ToList(); | ||
| } | ||
|
|
||
| private static void CollectPropertyNames(JsonElement element, List<string> names) | ||
| { | ||
| if (element.ValueKind == JsonValueKind.Object) | ||
| { | ||
| foreach (JsonProperty property in element.EnumerateObject()) | ||
| { | ||
| if (property.Name == "properties") | ||
| { | ||
| foreach (JsonProperty item in property.Value.EnumerateObject()) | ||
| { | ||
| names.Add(item.Name); | ||
| CollectPropertyNames(item.Value, names); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| CollectPropertyNames(property.Value, names); | ||
| } | ||
| } | ||
| } | ||
| else if (element.ValueKind == JsonValueKind.Array) | ||
| { | ||
| foreach (JsonElement item in element.EnumerateArray()) | ||
| { | ||
| CollectPropertyNames(item, names); | ||
| } | ||
| } | ||
| } |
| var names = new List<string>(); | ||
| if (tool.JsonSchema is JsonElement schema) | ||
| { | ||
| CollectPropertyNames(schema, names); | ||
| } | ||
|
|
||
| return names.Distinct().ToList(); | ||
| } | ||
|
|
||
| private static void CollectPropertyNames(JsonElement element, List<string> names) | ||
| { | ||
| if (element.ValueKind == JsonValueKind.Object) | ||
| { | ||
| foreach (JsonProperty property in element.EnumerateObject()) | ||
| { | ||
| if (property.Name == "properties") | ||
| { | ||
| foreach (JsonProperty item in property.Value.EnumerateObject()) | ||
| { | ||
| names.Add(item.Name); | ||
| CollectPropertyNames(item.Value, names); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| CollectPropertyNames(property.Value, names); | ||
| } | ||
| } | ||
| } | ||
| else if (element.ValueKind == JsonValueKind.Array) | ||
| { | ||
| foreach (JsonElement item in element.EnumerateArray()) | ||
| { | ||
| CollectPropertyNames(item, names); | ||
| } | ||
| } |
| #pragma warning restore MAAI001 | ||
|
|
||
| AIContext result = await provider.InvokingAsync(context); | ||
| return (AIFunction)result.Tools!.First(t => t is AIFunction f && f.Name == toolName); |
| /// Verifies that each file access tool's description refers to arguments by the same | ||
| /// names that <see cref="AIFunctionFactory"/> exposes in the generated JSON schema. | ||
| /// </summary> | ||
| public class FileAccessToolDescriptionSchemaTests |
There was a problem hiding this comment.
Thanks for adding this fix!
Not sure that we really need these two new tests files though. It's a lot of test code to maintain for something fairly trivial. Let's remove them.
|
Thanks for the contribution Ruiming Zhao (@uuzzrm) |
Motivation & Context
The
file_accessandfile_memorytool descriptions refer to their arguments by snake_case names (glob_pattern,old_string,new_string,replace_all) whileAIFunctionFactorygenerates the tool schema from the camelCase C# parameter names (globPattern,oldString,newString,replaceAll). A model that follows the description emits argument names that never bind, sofile_access_lsruns without its filter andfile_access_replacefails its own "old_string not found" check for reasons unrelated to the file.Description & Review Guide
What are the major changes?
Renamed the argument references in the
[Description]text of the affected tools so they match the generated schema:file_access_ls,file_memory_ls,file_memory_grep:glob_pattern→globPatternfile_access_replace,file_memory_replace:old_string/new_string/replace_all→oldString/newString/replaceAllThe
replace_linesdescriptions are intentionally left alone:line_numberandnew_lineare mapped explicitly via[JsonPropertyName]and are genuinely snake_case in the schema.Added
FileAccessToolDescriptionSchemaTestsandFileMemoryToolDescriptionSchemaTestsasserting each affected description and its generated schema agree on argument names, plus a guard for thereplace_linesnames.What is the impact of these changes?
Models that follow the descriptions will emit argument names that bind, so filtering and replace behave as described instead of silently dropping arguments.
What do you want reviewers to focus on?
That every affected tool is covered, and that the
replace_linesJsonPropertyNamenames were intentionally kept as-is.Related Issue
Fixes #7672
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.